/*
Copyright (C) 1997-2008 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )

http://www.zsnes.com
http://sourceforge.net/projects/zsnes
https://zsnes.bountysource.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "../cfg.h"
#include "../gblhdr.h"
#include "../link.h"
#include "../video/copyvwin.h"
#include <stdint.h>


// FUNCTIONS
void hq2x_16b();

// VIDEO VARIABLES
extern SDL_Surface *surface;
extern int SurfaceLocking;
extern uint64_t BitDepth;

// OPENGL VARIABLES
static unsigned short *glvidbuffer = 0;
static GLuint gltextures[4];
static int gltexture256, gltexture512;
static int glfilters = GL_NEAREST;
static int glscanready = 0;
extern Uint8 GUIOn2;

extern unsigned short *vidbuffer;
extern unsigned char curblank;
extern uint8_t GUIRESIZE[];

void gl_clearwin();

void gl_scanlines();

char CheckOGLMode();

#if (SDL_MAJOR_VERSION > 1) || ((SDL_MINOR_VERSION > 2) || ((SDL_MINOR_VERSION == 2) && (SDL_PATCHLEVEL >= 10)))
char allow_glvsync = 1;
#else
char allow_glvsync = 0;
#endif

void SetGLAttributes()
{
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
#if SDL_VERSION_ATLEAST(1, 2, 10)
  int const value = vsyncon ? 1 : 0;
#	if SDL_VERSION_ATLEAST(1, 3, 0)
  SDL_GL_SetSwapInterval(value);
#else
  SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, value);
#	endif
  SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
#endif
}

int gl_start(int width, int height, int req_depth, int FullScreen)
{
  uint32_t flags = SDL_OPENGL;
  int i;

  flags |= (GUIRESIZE[cvidmode] ? SDL_RESIZABLE : 0);
  flags |= (FullScreen ? SDL_FULLSCREEN : 0);

  if (BilinearFilter)
  {
    glfilters = GL_LINEAR;
    if (GUIOn2 && !FilteredGUI)
    {
      glfilters = GL_NEAREST;
    }
  }
  else
  {
    glfilters = GL_NEAREST;
  }

  SurfaceX = width; SurfaceY = height;
  SetGLAttributes();
  surface = SDL_SetVideoMode(SurfaceX, SurfaceY, req_depth, flags);
  if (surface == NULL)
  {
    fprintf(stderr, "Could not set %dx%d-GL video mode.\n", SurfaceX, SurfaceY);
    return false;
  }

  if (!glvidbuffer)
  {
    glvidbuffer = (unsigned short*)malloc(512 * 512 * sizeof(short));
  }
  gl_clearwin();
  SDL_WarpMouse(SurfaceX / 4, SurfaceY / 4);

  // Grab mouse in fullscreen mode
  FullScreen ? SDL_WM_GrabInput(SDL_GRAB_ON) : SDL_WM_GrabInput(SDL_GRAB_OFF);

  SDL_WM_SetCaption("ZSNES", "ZSNES");
  SDL_ShowCursor(0);

  /* Setup some GL stuff */

  glEnable(GL_TEXTURE_1D);
  glEnable(GL_TEXTURE_2D);

  glViewport(0, 0, SurfaceX, SurfaceY);

  /*
   * gltextures[0]: 2D texture, 256x224
   * gltextures[1]: 2D texture, 512x224
   * gltextures[3]: 1D texture, 256 lines of alternating alpha
   */
  glGenTextures(4, gltextures);
  for (i = 0; i < 3; i++)
  {
    glBindTexture(GL_TEXTURE_2D, gltextures[i]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glfilters);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glfilters);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  }

  if (sl_intensity)
  {
    gl_scanlines();
  }

  return true;
}

void gl_end()
{
  if (glvidbuffer)
  {
    glDeleteTextures(4, gltextures);
    free(glvidbuffer);
    glvidbuffer = 0;
  }
}

extern unsigned char NGNoTransp;
extern unsigned char SpecialLine[224];  /* 0 if lo-res, > 0 if hi-res */

void gl_clearwin()
{
  glClear(GL_COLOR_BUFFER_BIT);
  memset(glvidbuffer, 0, 512 * 448 * 2);
}

/* gl_drawspan:
 * Puts a quad on the screen for hires/lores portions, starting at line start,
 * and ending at line end..
 * Builds the 256x256/512x256 textures if gltexture256 or gltexture512 == 0
 */
static void gl_drawspan(int hires, int start, int end)
{
  int i, j;

  switch (hires)
  {
    case 0:
      break;
    case 3:
    case 7:
      hires = 2;
      break;
    default:
      hires = 1;
      break;
  }

  if (hires)
  {
    if (hires != gltexture512)
    {
      unsigned short *vbuf1 = vidbuffer+16;
      unsigned short *vbuf2 = vidbuffer+(75036 * 2 + 16);
      unsigned short *vbuf = glvidbuffer;

      if (hires > 1) // mode 7
      {
        for (j = 224; j--;)
        {
          for (i = 256; i--;)
          {
            *vbuf++ = *vbuf1++;
          }
          for (i = 256; i--;)
          {
            *vbuf++ = *vbuf2++;
          }
          vbuf1 += 32;
          vbuf2 += 32;
        }
        glBindTexture(GL_TEXTURE_2D, gltextures[1]);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, 256, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
                     glvidbuffer);

        gltexture512 = 2;
      }
      else
      {
        for (j = 224; j--;)
        {
          for (i = 256; i--;)
          {
            *vbuf++ = *vbuf1++;
            *vbuf++ = *vbuf2++;
          }
          vbuf1 += 32;
          vbuf2 += 32;  // skip the two 16-pixel-wide columns
        }

        glBindTexture(GL_TEXTURE_2D, gltextures[1]);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, 512, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
                     glvidbuffer);

        gltexture512 = 1;
      }
    }

    glBindTexture(GL_TEXTURE_2D, gltextures[1]);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, (224.0 / 256.0) * (start / 224.0));
    glVertex2f(-1.0f, (112 - start) / 112.0);
    glTexCoord2f(1.0f, (224.0 / 256.0) * (start / 224.0));
    glVertex2f(1.0f, (112 - start) / 112.0);
    glTexCoord2f(1.0f, (224.0 / 256.0) * (end / 224.0));
    glVertex2f(1.0f, (112 - end) / 112.0);
    glTexCoord2f(0.0f, (224.0 / 256.0) * (end / 224.0));
    glVertex2f(-1.0f, (112 - end) / 112.0);
    glEnd();
  }
  else
  {
    glBindTexture(GL_TEXTURE_2D, gltextures[0]);
    if (!gltexture256)
    {
      glPixelStorei(GL_UNPACK_SKIP_PIXELS, 16);
      glPixelStorei(GL_UNPACK_ROW_LENGTH, 288);

      glTexImage2D(GL_TEXTURE_2D, 0, 3, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
                   vidbuffer + 288);

      glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
      glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);

      gltexture256 = 1;
    }

    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, (224.0 / 256.0) * (start / 224.0));
    glVertex2f(-1.0f, (112 - start) / 112.0);
    glTexCoord2f(1.0f, (224.0 / 256.0) * (start / 224.0));
    glVertex2f(1.0f, (112 - start) / 112.0);
    glTexCoord2f(1.0f, (224.0 / 256.0) * (end / 224.0));
    glVertex2f(1.0f, (112 - end) / 112.0);
    glTexCoord2f(0.0f, (224.0 / 256.0) * (end / 224.0));
    glVertex2f(-1.0f, (112 - end) / 112.0);
    glEnd();
  }
}

void gl_drawwin()
{
  int i;

  NGNoTransp = 0;    // Set this value to 1 within the appropriate
  // video mode if you want to add a custom
  // transparency routine or hardware
  // transparency.  This only works if
  // the value of newengen is equal to 1.
  // (see ProcessTransparencies in newgfx16.asm
  //  for ZSNES' current transparency code)
  UpdateVFrame();
  if (curblank || !CheckOGLMode())
  {
    return;
  }

  if (SurfaceX >= 512 && (hqFilter || En2xSaI))
  {
    AddEndBytes = 0;
    NumBytesPerLine = 1024;
    WinVidMemStart = (void*)glvidbuffer;

    if (hqFilter)
    {
      hq2x_16b();
    }
    else
    {
      copy640x480x16bwin();
    }

    /* Display 1 512x448 quad for the 512x448 buffer */
    glBindTexture(GL_TEXTURE_2D, gltextures[1]);
    glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
                 glvidbuffer);

    glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    glDisable(GL_BLEND);

    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glTexCoord2f(1.0f, 448.0f / 512.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 448.0f / 512.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glEnd();
  }
  else
  {
    /*
     * This code splits the hires/lores portions up, and draws
     * them with gl_drawspan
     */
    int lasthires, lasthires_line = 0;

    gltexture256 = gltexture512 = 0;

    lasthires = SpecialLine[1];
    for (i = 0; i < 224; i++)
    {
      if (SpecialLine[i + 1])
      {
        if (lasthires)
        {
          continue;
        }
        gl_drawspan(lasthires, lasthires_line, i);

        lasthires = SpecialLine[i + 1];
        lasthires_line = i;
      }
      else
      {
        if (!lasthires)
        {
          continue;
        }
        gl_drawspan(lasthires, lasthires_line, i);

        lasthires = SpecialLine[i + 1];
        lasthires_line = i;
      }
    }

    if (i - lasthires_line > 1)
    {
      gl_drawspan(lasthires, lasthires_line, i);
    }

    /*
     * This is here rather than right outside this if because the
     * GUI doesn't allow scanlines to be selected while filters are
     * on.. There is no technical reason they can't be on while
     * filters are on, however.  Feel free to change the GUI, and
     * move this outside the if (En2xSaI) {}, if you do.
     */
    if (sl_intensity)
    {
      glDisable(GL_TEXTURE_2D);
      glEnable(GL_BLEND);

      if (sl_intensity != glscanready)
      {
        gl_scanlines();
      }

      glBlendFunc(GL_DST_COLOR, GL_ZERO);
      glBindTexture(GL_TEXTURE_1D, gltextures[3]);
      glBegin(GL_QUADS);
      for (i = 0; i < SurfaceY; i += 256)
      {
        glTexCoord1f(0.0f);
        glVertex3f(-1.0f, (SurfaceY - i * 2.0) / SurfaceY, -1.0f);
        glTexCoord1f(0.0f);
        glVertex3f(1.0f, (SurfaceY - i * 2.0) / SurfaceY, -1.0f);
        glTexCoord1f(1.0f);
        glVertex3f(1.0f, (SurfaceY - (i + 256) * 2.0) / SurfaceY, -1.0f);
        glTexCoord1f(1.0f);
        glVertex3f(-1.0f, (SurfaceY - (i + 256) * 2.0) / SurfaceY, -1.0f);
      }
      glEnd();

      glDisable(GL_BLEND);
      glEnable(GL_TEXTURE_2D);
    }
  }
  SDL_GL_SwapBuffers();
}

void gl_scanlines()
{
  GLubyte scanbuffer[256][4];
  int i, j = (100 - sl_intensity) * 256 / 100;

  for (i = 0; i < 256; i += 2)
  {
    scanbuffer[i][0] = scanbuffer[i][1] = scanbuffer[i][2] = j;
    scanbuffer[i][3] = 0xFF;

    scanbuffer[i + 1][0] = scanbuffer[i + 1][1] = scanbuffer[i + 1][2] = 0xFF;
    scanbuffer[i + 1][3] = 0xFF;
  }

  glBindTexture(GL_TEXTURE_1D, gltextures[3]);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, scanbuffer);

  glscanready = sl_intensity;
}
